using System;
using System.Collections.Generic;
using System.Data;

namespace PS.WebParts.Rollup.Core
{
    static class JoinProcessor
    {
        public static void InnerJoin(DataTable baseTable, DataTable joineeTable, DataRelation join)
        {
            Join(baseTable, joineeTable, null, join, true);
        }
        
        public static void InnerJoin(DataTable baseTable, DataTable joineeTable, DataRelation join, List<string> excludeFieldsJoinee)
        {
            Join(baseTable, joineeTable, excludeFieldsJoinee, join, true);
        }


        public static void LeftJoin(DataTable baseTable, DataTable joineeTable, DataRelation join)
        {
            Join(baseTable, joineeTable, null, join, false);
        }

        public static void LeftJoin(DataTable baseTable, DataTable joineeTable, DataRelation join, List<string> excludeFieldsJoinee)
        {
            Join(baseTable, joineeTable, excludeFieldsJoinee, join, false);
        }

       private static void Join(DataTable baseTable, DataTable joineeTable, List<string> excludeFieldsJoinee, DataRelation join, bool doInnerJoin)
        {
           DynamicCorrelateRelation dynJoin = join as DynamicCorrelateRelation;
           
            if (baseTable == null)
                throw new ArgumentNullException("baseTable");
            if (joineeTable == null)
            {
                if (dynJoin == null)
                    throw new ArgumentNullException("joineeTable");
                else
                    joineeTable = new DataTable();
            }
            
            if (excludeFieldsJoinee == null)
                excludeFieldsJoinee = new List<string>();

            List<string> columns = new List<string>(baseTable.Columns.Count + joineeTable.Columns.Count);
            List<string> baseColumns = new List<string>(baseTable.Columns.Count );

            Dictionary<string, string> joineeFields = new Dictionary<string, string>(joineeTable.Columns.Count);

            foreach (DataColumn col in baseTable.Columns)
            {
                columns.Add(col.ColumnName);
                baseColumns.Add(col.ColumnName);
            }

            foreach (DataColumn col in joineeTable.Columns)
            {
                string name = col.ColumnName;
                if (!excludeFieldsJoinee.Contains(name))
                {
                    string n = GetSafeName(columns, name);
                    DataColumn c = new DataColumn(n, col.DataType, col.Expression);
                    object explType = col.ExtendedProperties[FieldInfo.ExtPropKey];
                    if (explType != null)
                        c.ExtendedProperties[FieldInfo.ExtPropKey] = explType;
                    baseTable.Columns.Add(c);
                    columns.Add(n);
                    joineeFields.Add(name, n);
                }
            }

            List<DataRow> baseRows = Clone(baseTable.Rows);
            baseTable.Constraints.Clear();
           bool dynCorellateSchemaCopied = false;

           for (int a = 0; a < baseRows.Count; a++ )
            {
                DataRow row = baseRows[a];
                DataRow[] rows = dynJoin != null ? dynJoin.GetChildRows(row) : GetChildRows(row, join); // row.GetChildRows(join);
                if (dynJoin != null && !dynCorellateSchemaCopied)
                {
                    DataTable table = dynJoin.GetResultingTable();
                    if (table != null && table.Rows.Count > 0)
                    foreach (DataColumn column in table.Columns)
                    {
                        if (column.ExtendedProperties.ContainsKey(FieldInfo.ExtPropKey) &&
                            joineeFields.ContainsKey(column.ColumnName))
                        {
                            string mappedName = joineeFields[column.ColumnName];
                            object o = column.ExtendedProperties[FieldInfo.ExtPropKey];
                            baseTable.Columns[mappedName].ExtendedProperties[FieldInfo.ExtPropKey] = o;
                            dynCorellateSchemaCopied = true;
                        }
                    }
                }
                if (doInnerJoin && ( rows == null || rows.Length == 0 ) )
                {
                    row.Delete();
                }
                else
                {
                    int c = 0;
                    foreach (DataRow dataRow in rows)
                    {
                        DataRow baseRow;
                        
                        if (c == 0)
                        {
                            baseRow = row;
                        }
                        else
                        {
                            baseRow = baseTable.NewRow();
                            foreach (string baseCol in baseColumns)
                            {
                                baseRow[baseCol] = row[baseCol];
                            }
                        }
                        foreach (KeyValuePair<string, string> field in joineeFields)
                        {
                            baseRow[field.Value] = dataRow[field.Key];
                        }

                        if (c++ > 0)
                        {
                            DataRowCollection baseTableRows = baseTable.Rows;
                            int idx = baseTableRows.IndexOf(row);
                            baseTableRows.InsertAt(baseRow, idx + c -1);
                            //baseTableRows.Add(baseRow);
                        }
                    }
                }
            }
            
        }

        private static DataRow[] GetChildRows(DataRow row, DataRelation join)
        {
            DataRowCollection childRows = join.ChildTable.Rows;

            List<DataRow> rows = new List<DataRow>(childRows.Count / 2);

            DataColumn[] childColumns = join.ChildColumns;
            if (childColumns.Length!=1)
                throw new ArgumentException("Invalid DataRelation: child column count != 0");
            
            DataColumn childColumn = childColumns[0];

            DataColumn[] parentColumns = join.ParentColumns;

            if (parentColumns.Length != 1)
                throw new ArgumentException("Invalid DataRelation: parent column count != 0");

            DataColumn parentColumn = parentColumns[0];

            Type childDataType = childColumn.DataType;
            bool doConversion = parentColumn.DataType != childDataType;

            object parentValue = row[parentColumn];

            if (parentValue == null || parentValue == DBNull.Value)
                return null; // nothing to retrieve here

            // a few special cases to streamline conversion
            if (doConversion)
            {
                if (childDataType == typeof (int))
                {
                    int t;
                    if (int.TryParse(parentValue.ToString(), out t))
                    {
                        parentValue = t;
                        doConversion = false;
                    }
                }
                else if (childDataType == typeof (string))
                {
                    parentValue = parentValue.ToString();
                    doConversion = false;
                }
                if (childDataType == typeof(Guid))
                {
                    parentValue = new Guid( parentValue.ToString() );
                    doConversion = false;
                }
                else
                {
                    parentValue = parentValue.ToString();
                }
            }

            foreach (DataRow dr in childRows)
            {
                object childValue = dr[childColumn];

                if (childValue == null || childValue == DBNull.Value)
                    continue;

                if (doConversion)
                    childValue = childValue.ToString();

                if (parentValue.Equals(childValue))
                    rows.Add(dr);
            }

            return rows.ToArray();
        }

        public static DataTable Union(params DataTable[] tables)
        {
            if (tables == null)
                throw new ArgumentNullException("tables");
            
            List<DataTable> list = new List<DataTable>(tables.Length);
            foreach (DataTable table in tables)
            {
                list.Add(table);
            }
            return Union(list);
        }
        
        public static DataTable Union(List<DataTable> tables)
        {
            if (tables == null)
                throw new ArgumentNullException("tables");
            
            DataTable res = new DataTable();
            
            List<string> masterFields = new List<string>(128);
            Dictionary<string, string> map = new Dictionary<string, string>(128);

            foreach (DataTable table in tables)
            {
                //if (res.DataSet == null && table.DataSet != null)
                //    table.DataSet.Tables.Add(res);
                
                int code = table.GetHashCode();
                foreach (DataColumn column in table.Columns)
                {
                    string columnName = column.ColumnName;
                    string resolvedName=columnName;
                    if (!masterFields.Contains(columnName))
                    {
                        masterFields.Add(resolvedName);
                        DataColumn col = res.Columns.Add(resolvedName, column.DataType);
                        object explType = column.ExtendedProperties[FieldInfo.ExtPropKey];
                        if (explType != null)
                            col.ExtendedProperties[FieldInfo.ExtPropKey] = explType;
                    }
                    
                    map.Add(code + "_" + columnName, resolvedName);
                }

                foreach (DataRow row in table.Rows)
                {
                    DataRow resRow = res.NewRow();

                    foreach (DataColumn column in table.Columns)
                    {
                        string resCol = map[code + "_" + column.ColumnName];
                        resRow[resCol] = row[column];
                    }

                    res.Rows.Add(resRow);
                }
          }            
          return res;
        }


        private static List<DataRow> Clone(DataRowCollection dataRowCollection)
        {
            List<DataRow> list = new List<DataRow>(dataRowCollection.Count);

            foreach (DataRow row in dataRowCollection)
            {
                list.Add(row);
            }
            return list;
        }

        private static string GetSafeName(List<string> columns, string name)
        {
            if (!columns.Contains(name))
                return name;
            
            int c = 0;
            while(c < 1000)
            {
                string n = name + "_" + ++c;
                if (!columns.Contains(n))
                    return n;
            }

            throw new ArgumentException("Cannot allocate unique name for the base table: " + name, "name");
        }

    }
}
